#include "PhysicsLogic.h"

#include "PhysxConsoleOutputStream.h"
#include "PhysxContactReport.h"
#include "Sound.h"
#include "Camera.h"
#include "DebugRenderer.h"
#include "Renderer.h"
#include "Game.h"
#include "Hud.h"
#include "Stream.h"
#include "ShapeData.h"

#include "Util/PerformanceTest.h"
#include "Util/Physx.h"
#include "Util/Round.h"

#include "Entities/ActorEntities/Cannon.h"
#include "Entities/ActorEntities/CannonBall.h"
#include "Entities/ActorEntities/Building.h"
#include "Entities/ActorEntities/Ground.h"

#include "Entities/Explosion.h"
#include "Entities/Grass.h"

#include "WrappedContainers/LinkedList.h"
#include "WrappedContainers/DynamicArray3.h"

#include <NxCooking.h>

#include <sstream>
#include <limits>
#include <math.h>

#define CLASSNAME_LOG "PhysicsLogic"

NxPhysicsSDK *PhysicsLogic::physicsSDK = NULL;
NxScene *PhysicsLogic::scene = NULL;
DebugRenderer PhysicsLogic::debugRenderer;
PerformanceTest PhysicsLogic::pt;

Light PhysicsLogic::sunLight(GL_LIGHT0);

Camera PhysicsLogic::camera(NxVec3(0.0f, 16.0f, -16.0f), NxVec3(-0.7f, -0.3f, 0.0f), 1.0);
Camera PhysicsLogic::hudCamera(NxVec3(0.0f, 16.0f, -16.0f), NxVec3(0.0f, -0.3f, 0.7f), 1.0);

NxVec3 PhysicsLogic::cameraVelocity(0,0,0);

NxVec3 PhysicsLogic::cameraBounds(180,50,180);

Skybox* PhysicsLogic::skybox = NULL;

const float PhysicsLogic::flagViewW = 208-6;
const float PhysicsLogic::flagViewH = 189-6;

NxVec3 PhysicsLogic::wind(0,0,0);

float PhysicsLogic::hudMessageTimeToLive = 0.0f;
const float PhysicsLogic::hudMessageMaxTimeToLive = 3.0f;
std::string PhysicsLogic::hudMessage;

CannonBall* PhysicsLogic::observedCannonBall = NULL;
bool PhysicsLogic::cannonBallCameraEnabled = true;
bool PhysicsLogic::cameraLocked = true;

SurfaceRenderingData PhysicsLogic::textMaterial;
SurfaceRenderingData PhysicsLogic::textShadowMaterial;
SurfaceRenderingData PhysicsLogic::helpPanelMaterial;
SurfaceRenderingData PhysicsLogic::buttonMaterial;
SurfaceRenderingData PhysicsLogic::namePanelMaterial;
SurfaceRenderingData PhysicsLogic::bottomPanelMaterial;
SurfaceRenderingData PhysicsLogic::flagPanelMaterial;
SurfaceRenderingData PhysicsLogic::flagPanelMaterial2;
SurfaceRenderingData PhysicsLogic::buttonFreeCamMaterial;
SurfaceRenderingData PhysicsLogic::buttonFixedCamMaterial;

int PhysicsLogic::lastClickGlTime = -1;
int PhysicsLogic::lastClickX = 0;
int PhysicsLogic::lastClickY = 0;
int PhysicsLogic::mouseButtonState[] = { GLUT_UP, GLUT_UP, GLUT_UP };
int PhysicsLogic::lastMousePosX = -1;
int PhysicsLogic::lastMousePosY = -1;

bool PhysicsLogic::Init() {
	Hud::Init();

	NxPhysicsSDKDesc sdkDesc;
	sdkDesc.flags &= ~NX_SDKF_NO_HARDWARE; //enable hw
	sdkDesc.flags |= NX_BF_ENERGY_SLEEP_TEST;

	physicsSDK = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION, 0, &physxConsoleOutputStream, sdkDesc);
	if(physicsSDK == NULL) {
		//try again without hardware acceleration:
		sdkDesc.flags |= NX_SDKF_NO_HARDWARE; //disable hw
		physicsSDK = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION, 0, &physxConsoleOutputStream, sdkDesc);
	}

	if (physicsSDK == NULL) {
		Game::log.LogError(CLASSNAME_LOG ".InitNx: could not create physics SDK");
		return false;
	}

	Game::log.LogInfo(StringBuilder() << "Hardware Acceleration: " << IsHardwareAccelerationSupported());

	physicsSDK->setParameter(NX_SKIN_WIDTH, 0.0030f);

	physicsSDK->setParameter(NX_DEFAULT_SLEEP_ENERGY, 0.2f);
	/*
	const float slvs = 15.0f*100000;
	const float savs = 14.0f*100000;
	physicsSDK->setParameter(NX_DEFAULT_SLEEP_LIN_VEL_SQUARED, slvs*slvs);
	physicsSDK->setParameter(NX_DEFAULT_SLEEP_ANG_VEL_SQUARED, savs*savs);
	*/

	physicsSDK->setParameter(NX_CONTINUOUS_CD, 1); //enable CCD
	

	// Create a scene
	NxSceneDesc sceneDesc;
	sceneDesc.gravity = NxVec3(0.0f, -9.81f, 0.0f);
	sceneDesc.userContactReport = &PhysxContactReport::physxContactReport;
    sceneDesc.flags |= NX_SF_SIMULATE_SEPARATE_THREAD; //enable Threading
    sceneDesc.flags |= NX_SF_ENABLE_MULTITHREAD;       //
	scene = physicsSDK->createScene(sceneDesc);
	if(scene == NULL) {
		Game::log.LogError(CLASSNAME_LOG ".InitNx: could not create a PhysX scene");
		return false;
	}

	// Set default material
	NxMaterial* defaultMaterial = scene->getMaterialFromIndex(0);
	/*
	defaultMaterial->setRestitution(0.0f);
	defaultMaterial->setStaticFriction(0.9f);
	defaultMaterial->setDynamicFriction(0.9f);
	*/
	defaultMaterial->setStaticFriction(1.0f);
	defaultMaterial->setDynamicFriction(1.0f);

	// Set Contact Report
	//scene->setActorGroupPairFlags(1, 1, NX_NOTIFY_ON_TOUCH_FORCE_THRESHOLD);
	scene->setActorGroupPairFlags(1, 1, NX_NOTIFY_ON_START_TOUCH);

	// Create ground plane
	if(false) {
		NxPlaneShapeDesc planeDesc;
		NxActorDesc actorDesc;
		actorDesc.shapes.pushBack(&planeDesc);
		scene->createActor(actorDesc)->setGroup(1);
	}

	// Create walls
	if(false) {
		NxPlaneShapeDesc wall1;
		NxPlaneShapeDesc wall2;
		NxPlaneShapeDesc wall3;
		NxPlaneShapeDesc wall4;
		const float wallsDistFromOrigin = -0.8f;
		wall1.normal = NxVec3(1,0,0);
		wall1.d = wallsDistFromOrigin;
		wall2.normal = NxVec3(-1,0,0);
		wall2.d = wallsDistFromOrigin;
		wall3.normal = NxVec3(0,0,1);
		wall3.d = wallsDistFromOrigin;
		wall4.normal = NxVec3(0,0,-1);
		wall4.d = wallsDistFromOrigin;
		NxActorDesc wallActorDesc;
		wallActorDesc.shapes.pushBack(&wall1);
		wallActorDesc.shapes.pushBack(&wall2);
		wallActorDesc.shapes.pushBack(&wall3);
		wallActorDesc.shapes.pushBack(&wall4);
		scene->createActor(wallActorDesc);
	}

	//Light
	const float ambient = 0.4f;
	sunLight.SetAmbient(ambient, ambient, ambient);
	sunLight.SetDiffuse(1.0f, 1.0f, 1.0f);
	sunLight.SetSpecular(1.0f, 1.0f, 1.0f);
	//sunLight.SetPosition(10.0f, 10.0f, 10.0f, 1.0f); //point light
	//sunLight.SetPosition(0.0f, 1.0f, -0.5f, 0.0f); //directional light
	//NxVec3 lightRayVec(0.3f, 0.4f, 0.5f);
	NxVec3 lightRayVec(0.1f, 0.5f, 0.8f);
	lightRayVec.normalize();
	sunLight.SetPosition(lightRayVec.x, lightRayVec.y, lightRayVec.z, 0.0f); //directional light
	Renderer::SetLightState(sunLight, true);

	//Debug Renderer
	debugRenderer.Init(physicsSDK, scene);
	debugRenderer.SetScale(0.25f);

	//Skybox
	skybox = new Skybox("res/textures/skybox/", 32000.0f, 32000.0f, 32000.0f);

	Player::Load();
	Ground::Load();
	Building::Load();
	Cannon::Load();
	CannonBall::Load();
	Explosion::Load();
	Flag::Load();
	Grass::Load();

	textMaterial.SetColor(1.0f, 0.85f, 0.0f, 1.0f);
	textShadowMaterial.SetColor(0,0,0,1.0f);
	helpPanelMaterial.SetColor(1,1,1,0.95f);
	buttonMaterial.SetTexture(Textures::TEXTURE_BUTTON);
	namePanelMaterial.SetTexture(Textures::TEXTURE_NAME_PANEL);
	bottomPanelMaterial.SetTexture(Textures::TEXTURE_BOTTOM_PANEL);
	flagPanelMaterial.SetTexture(Textures::TEXTURE_FLAG_PANEL);
	flagPanelMaterial2.SetColor(1,1,1,0.7f);
    buttonFreeCamMaterial.SetTexture(Textures::TEXTURE_BUTTON_FREE_CAM);
    buttonFixedCamMaterial.SetTexture(Textures::TEXTURE_BUTTON_FIXED_CAM);

	ToggleFlag();
	//ToggleGrass(false);

	return true;
}

void PhysicsLogic::Exit() {
	if(physicsSDK != NULL) {
		if(scene != NULL) {
			for(int i=0; i<Flag::all.Size(); ++i) {
				delete Flag::all[i];
			}
			Flag::all.Clear();

			physicsSDK->releaseScene(*scene);
		}
		scene = NULL;
		NxReleasePhysicsSDK(physicsSDK);
		physicsSDK = NULL;
	}

	delete skybox;
	skybox = NULL;
}

bool PhysicsLogic::IsHardwareAccelerationSupported() {
	if(physicsSDK == NULL) {
		return false;
	}
	return physicsSDK->getHWVersion() != NX_HW_VERSION_NONE;
}

NxVec3 PhysicsLogic::GetCameraForward() {
	NxVec3 r = camera.GetDirection();
	r.y = 0;
	r.normalize();
	return r;
}

void PhysicsLogic::ClearScene() {
	Ground::toDelete = Ground::all;

	while(!Cannon::destoyed.Empty()) {
		Cannon* cannon = Cannon::destoyed.GetFirst();
		delete cannon;
		Cannon::destoyed.RemoveFirst();
	}
	for(int p=0; p<2; ++p) {
		ArrayList<Cannon*>& cannons = Game::GetPlayer(p)->cannons;
		for(int i=0; i < cannons.Size(); ++i) {
			delete cannons[i];
		}
		cannons.Clear();

		ArrayList<Building*>& building = Game::GetPlayer(p)->buildings;
		for(int i=0; i < building.Size(); ++i) {
			delete building[i];
		}
		building.Clear();
	}

	//cannon balls
	CannonBall::DeleteDestroyed();
	ArrayList<CannonBall*>& cannonBalls = CannonBall::all;
	for(int i=0; i < cannonBalls.Size(); ++i) {
		delete cannonBalls[i];
	}
	cannonBalls.Clear();

	int nbActors = scene->getNbActors();
	NxActor** actors = scene->getActors();
	while(nbActors--) {
		NxActor* actor = *actors++;
		NxShape* const* shapes = actor->getShapes();
		ActorEntity* entity = (ActorEntity*)actor->userData;
		if(entity != NULL) {
			if(entity->GetType() != AET_GROUND) {
				delete entity;
			}
		} else {
			DeleteNonEntityActor(actor);
		}
	}
}

void PhysicsLogic::MakeCCD(NxShape* shape) {
	NxVec3 dim;
	if(shape->isBox()) {
		dim = shape->isBox()->getDimensions();
	} else if(shape->isSphere()) {
		dim.set(1, 1, 1);
		dim.setMagnitude(shape->isSphere()->getRadius() * 0.9f); 
	} else {
		NX_ASSERT(!"Invalid shape type");
	}

	const int verticesCount = 8;
	const int trianglesCount = 2*6;
	const NxBox obb(zeroVec3, dim, NxMat33(NX_IDENTITY_MATRIX));

	NxSimpleTriangleMesh triMesh;
	triMesh.points = new NxVec3[verticesCount];
	triMesh.numVertices = verticesCount;
	triMesh.pointStrideBytes = sizeof(NxVec3);
	triMesh.numTriangles = trianglesCount;
	triMesh.triangles = new NxU32[trianglesCount*3];
	triMesh.triangleStrideBytes = sizeof(NxU32)*3;
	triMesh.flags = 0;
	NxComputeBoxPoints(obb, (NxVec3*)triMesh.points);
	memcpy((NxU32*)triMesh.triangles, NxGetBoxTriangles(), sizeof(NxU32)*trianglesCount*3);

	NX_ASSERT(triMesh.isValid());

	shape->setCCDSkeleton(physicsSDK->createCCDSkeleton(triMesh));
	shape->setFlag(NX_SF_DYNAMIC_DYNAMIC_CCD, true);

	delete[] triMesh.points;
	delete[] triMesh.triangles;
}

void PhysicsLogic::CreateGround() {
	const int planeDistance = 17;
	const float roughness = 50.0f;
	const int cellsPerTexture = 2;
	const NxVec3 scale(15.0f, 0.70f, 15.0f);
	new Ground(128, 128, planeDistance, roughness, cellsPerTexture, scale);
}

void PhysicsLogic::CreateBrick(const NxVec3& pos, const NxVec3& dim, const NxVec3& orientation) {
	// Create body
	NxBodyDesc bodyDesc;
	bodyDesc.angularDamping = 0.1f;
	bodyDesc.linearDamping = 0.1f;

	NxMat33 rotationMatrix;
	rotationVectorToMatrix(orientation, &rotationMatrix);

	NxBoxShapeDesc boxDesc;
	boxDesc.dimensions = dim;
	Textures::TEXTURE brickTexture = (Textures::TEXTURE)(Textures::TEXTURE_BRICK + rand()%Textures::BRICKS_COUNT);
	const float cr = 0.97f + (rand()%4)*0.01f;
	const float cg = 0.97f + (rand()%4)*0.01f;
	const float cb = 0.97f + (rand()%4)*0.01f;
    ShapeData *sd = new ShapeData();
	SurfaceRenderingData* srd = new SurfaceRenderingData();
	srd->SetTexture(brickTexture);
	srd->SetColor(cr, cg, cb, 1.0f);
    sd->srd = srd;
	boxDesc.userData = sd;

	NxActorDesc actorDesc;
	actorDesc.shapes.pushBack(&boxDesc);
	actorDesc.body = &bodyDesc;
	actorDesc.density = 1.0f;
	actorDesc.globalPose.t = pos;
	actorDesc.globalPose.M = rotationMatrix;
	NxActor* actor = scene->createActor(actorDesc);

	actor->setGroup(1);
	actor->raiseBodyFlag(NX_BF_KINEMATIC);
	
	//actor->setContactReportFlags(NX_NOTIFY_ON_START_TOUCH_FORCE_THRESHOLD);
	//actor->setContactReportFlags(NX_NOTIFY_ON_TOUCH_FORCE_THRESHOLD);
	//actor->setContactReportThreshold(0.5f);
	actor->setContactReportFlags(NX_NOTIFY_FORCES);

	//CCD
	PhysicsLogic::MakeCCD(actor->getShapes()[0]);
	actor->setCCDMotionThreshold(10.0f);
}

void PhysicsLogic::CreateWall(const NxVec3& pos, float brickSize, int length, int height, float angle, bool fillHoles) {
	const float brickSize2 = brickSize * 2;
	const float t1 = brickSize2*length + brickSize;
	const float t2 = -brickSize2*length - brickSize;
	const NxVec3 posC(pos.x - brickSize, pos.y, pos.z - brickSize);
	const NxVec3 brickDim(brickSize2, brickSize, brickSize);
	const NxVec3 halfBrickDim(brickSize, brickSize, brickSize);
	const NxVec3 orientation(0,angle,0);
	for(int h=0; h<height; ++h) {
		const float y = brickSize + 4*h*brickSize;
		for(int i=0; i<length; ++i) {
			const float t3 = -brickSize2*length + brickSize2 + 4*brickSize*i;
			CreateBrick(posC + NxVec3(cos(angle)* t3,               y,              sin(angle) *  t3),               brickDim, orientation);
			CreateBrick(posC + NxVec3(cos(angle)*(t3 + brickSize2), y + brickSize2, sin(angle) * (t3 + brickSize2)), brickDim, orientation);
		}
		if(fillHoles) {
			CreateBrick(posC + NxVec3(cos(angle)* t1,               y,              sin(angle) *  t1),               halfBrickDim, orientation);
			CreateBrick(posC + NxVec3(cos(angle)*(t2 + brickSize2), y + brickSize2, sin(angle) * (t2 + brickSize2)), halfBrickDim, orientation);
		}
	}
}

void PhysicsLogic::CreateTower(const NxVec3& pos, float brickSize, int wallLength, int wallHeight) {
	const float brickSize2 = brickSize*2;
	const float distFromCenter = brickSize2*wallLength + brickSize;
	CreateWall(pos+NxVec3(brickSize2, 0, distFromCenter),               brickSize, wallLength, wallHeight, NxPiF32,      false);
	CreateWall(pos+NxVec3(distFromCenter, 0, 0),                        brickSize, wallLength, wallHeight, NxPiF32*0.5f, false);
	CreateWall(pos+NxVec3(0, 0, -distFromCenter + brickSize2),          brickSize, wallLength, wallHeight, 0,            false);
	CreateWall(pos+NxVec3(-distFromCenter + brickSize2, 0, brickSize2), brickSize, wallLength, wallHeight, NxPiF32*1.5f, false);

	//make roof:

	const float roofHeight = 0.1f;
	const float roofHeight2 = roofHeight*3;

	NxBodyDesc bd;
	bd.angularDamping = 0.1f;
	bd.linearDamping = 0.1f;

	NxBoxShapeDesc bsd1;
	bsd1.dimensions = NxVec3(distFromCenter, roofHeight, distFromCenter);
    ShapeData *sd1 = new ShapeData();
	SurfaceRenderingData* srd1 = new SurfaceRenderingData();
    srd1->SetTexture(Textures::TEXTURE_WOODEN_FRAME);
	sd1->srd = srd1;
	bsd1.userData = sd1;

	NxBoxShapeDesc bsd2;
	bsd2.dimensions = NxVec3(distFromCenter - brickSize2, roofHeight2, distFromCenter - brickSize2);
	ShapeData *sd2 = new ShapeData();
	SurfaceRenderingData* srd2 = new SurfaceRenderingData();
	srd2->SetTexture(Textures::TEXTURE_WOODEN_FRAME);
	sd2->srd = srd2;
	bsd2.userData = sd2;
	bsd2.localPose.t.y = -roofHeight2;

	NxActorDesc ad;
	ad.shapes.pushBack(&bsd1);
	ad.shapes.pushBack(&bsd2);
	ad.body = &bd;
	ad.density = 1.0f;
	ad.globalPose.t = pos + NxVec3(0, wallHeight * brickSize * 4 + roofHeight, 0);
	NxActor* roofActor = scene->createActor(ad);

	roofActor->setGroup(1);
	roofActor->raiseBodyFlag(NX_BF_KINEMATIC);
	roofActor->setContactReportFlags(NX_NOTIFY_FORCES);
}

void PhysicsLogic::DeleteNonEntityActor(NxActor* actor) {
	const int numberOfShapes = actor->getNbShapes();
	for(int i=0; i<numberOfShapes; ++i) {
		NxShape* pickedShape = actor->getShapes()[i];
		if(pickedShape->userData) {
			delete pickedShape->userData;
		}
	}
	scene->releaseActor(*actor);
}

void PhysicsLogic::PreTick() {
	if(scene == NULL) {
		return;
	}

	const int newTime = glutGet(GLUT_ELAPSED_TIME);
	const int deltaTime = newTime - Renderer::currentTime;
	const float elapsedTime = 0.001f * deltaTime;

    Renderer::frameCount++;
    Renderer::elapsedFpsTime += deltaTime;

    //fps
    if(Renderer::elapsedFpsTime >= 1000) {
        Renderer::fps = Renderer::frameCount / (Renderer::elapsedFpsTime / 1000.0f);
        Renderer::elapsedFpsTime = 0;
        Renderer::frameCount = 0;
    }

	Game::Tick();

	Sound::Tick(elapsedTime);

	//delete ground
	while(!Ground::toDelete.Empty()) {
		Ground* pickedGround = Ground::toDelete.GetFirst();
		Ground::toDelete.RemoveFirst();
		Ground::all.Remove(pickedGround);
		delete pickedGround;
	}

	const float cameraMaxVelocity = 5.0f;
	if(cameraVelocity.magnitude() > cameraMaxVelocity) {
		cameraVelocity.setMagnitude(cameraMaxVelocity);
	}
	cameraVelocity *= 0.80f;
	camera.MoveBy(cameraVelocity);

	//update players
	for(int i=0; i<2; ++i) {
		Game::GetPlayer(i)->Tick(elapsedTime);
	}

	//remove destroyed buildings
	if(true) {
		Building::DeleteDestroyed();
	}

	//remove destroyed cannons
	if(false) {
		for(int pi=0; pi<2; ++pi) {
			ArrayList<Cannon*>& cannons = Game::GetPlayer(pi)->cannons;
			for(int ci=0; ci<cannons.Size(); ++ci) {
				Cannon* cannon = cannons[ci];
				if(cannon->IsDestroyed()) {
					cannons.Remove(cannon);
					delete cannon;
				}
			}
		}
	}

	//cannon balls
	CannonBall::DeleteDestroyed();
	ArrayList<CannonBall*>& cannonBalls = CannonBall::all;
	for(int i=0; i<cannonBalls.Size(); ++i) {
		cannonBalls[i]->Tick();
	}

	//remove explosions
	while(!Explosion::toDelete.Empty()) {
		Explosion* explosion = Explosion::toDelete.GetFirst();
		Explosion::toDelete.RemoveFirst();
		Explosion::all.Remove(explosion);
		delete explosion;
	}

	//tick explosions
	for(int i=0; i<Explosion::all.Size(); ++i) {
		Explosion::all[i]->Tick(elapsedTime);
	}

	//camera lock
	if(cameraLocked) {
		Player* currentPlayer = Game::GetCurrentPlayer();
		if(currentPlayer->IsInBuildMode()) {
			NxVec3 pos = currentPlayer->GetPlacementBoxRenderPosition();
			camera.SetPosition(pos - camera.GetDirection()*30);
		} else {
			Cannon* cannon = currentPlayer->GetSelectedCannon();
			if(cannon) {
				NxVec3 pos = cannon->GetPosition();
				NxVec3 dir = cannon->GetDirection();
				dir.y = 0;
				dir.normalize();

				camera.SetDirection(dir);
				camera.SetPosition(pos + NxVec3(0,3,0) - dir*10);
			}
		}
	}

	//follow cannon ball
	if(observedCannonBall && cannonBallCameraEnabled) {
		const NxActor* actor = observedCannonBall->GetActor();
		NxVec3 dir = actor->getLinearVelocity();
		dir.normalize();
		dir.y *= 0.5f;
		camera.SetPosition(actor->getGlobalPosition() - dir*7);
		camera.SetDirection(dir);
	} else {
		//keep camera in bounds
		NxVec3 cameraPos = camera.GetPosition();

		if(cameraPos.x > cameraBounds.x) {
			cameraPos.x = cameraBounds.x;
		} else if(cameraPos.x < -cameraBounds.x) {
			cameraPos.x = -cameraBounds.x;
		}

		if(cameraPos.z > cameraBounds.z) {
			cameraPos.z = cameraBounds.z;
		} else if(cameraPos.z < -cameraBounds.z) {
			cameraPos.z = -cameraBounds.z;
		}

		if(cameraPos.y < 1) {
			cameraPos.y = 1;
		} else if(cameraPos.y > cameraBounds.y) {
			cameraPos.y = cameraBounds.y;
		}

		camera.SetPosition(cameraPos);
	}

	if(hudMessageTimeToLive > 0.0f) {
		hudMessageTimeToLive -= elapsedTime;
	}

	debugRenderer.Update(scene);

	// Start simulation (non blocking)
	scene->simulate(elapsedTime);

	Renderer::currentTime = newTime;
}

void PhysicsLogic::PostTick() {
	if(scene == NULL) {
		return;
	}

	// Fetch simulation results
	NxU32 errorCode;
    scene->flushStream();
	scene->fetchResults(NX_RIGID_BODY_FINISHED, true, &errorCode);
	if(errorCode != 0) {
		Game::log.LogError(StringBuilder() << "Simulation Error (fetch results): " << errorCode);
	}
}

void PhysicsLogic::Draw() {
	if(scene == NULL) {
		return;
	}

	//pt.Start();

	glViewport(0, 0, Renderer::GetActiveScreenW(), Renderer::GetActiveScreenH());

	Renderer::ClearBuffers();
	//pt.Next("clear buffers");

	//Camera
	Renderer::Update(camera);
	//pt.Next("camera");
	
	//Light
	glPushMatrix();
	Renderer::SetLight(sunLight);
	glPopMatrix();
	//pt.Next("light");

	//light ray in front of camera
	if(false) {
		glDisable(GL_LIGHTING);
		glBegin(GL_LINES);
			NxVec3 lrs = camera.GetPosition() + camera.GetDirection()*2;
			NxVec3 lre = lrs + 0.3f*NxVec3(sunLight.position[0], sunLight.position[1], sunLight.position[2]);
			SurfaceRenderingData lrcs; lrcs.SetColor(1,0,0,1);
			Renderer::SetMaterial(lrcs);
			glVertex3f(lrs.x, lrs.y, lrs.z);
			SurfaceRenderingData lrce; lrce.SetColor(0,1,0,1);
			Renderer::SetMaterial(lrce);
			glVertex3f(lre.x, lre.y, lre.z);
		glEnd();
		glEnable(GL_LIGHTING);
	}

	glDisable(GL_LIGHTING);
	skybox->Draw();
	glEnable(GL_LIGHTING);
	//pt.Next("skybox");
	
	if(false) { //debug plane ground
		SurfaceRenderingData groundMaterial;
		groundMaterial.SetTexture(Textures::TEXTURE_GROUND);
		Renderer::SetMaterial(groundMaterial);
		Renderer::DrawPlane(7.0f, 12, -0.01f);
	}

	//Players
	for(int i=0; i<2; ++i) {
		Game::GetPlayer(i)->Draw();
	}
	//pt.Next("ground + players");

    NxPlane *frustumCullingPlanes = GetCamera().GetPlanesOfView();
    int nbTotalCollidingShapes = scene->getTotalNbShapes();
    NxShape **intersectingShapes = new NxShape *[nbTotalCollidingShapes];
    nbTotalCollidingShapes = scene->cullShapes(6, frustumCullingPlanes, NX_ALL_SHAPES, nbTotalCollidingShapes, intersectingShapes, NULL);
    for(int i = 0; i < nbTotalCollidingShapes; ++i) {
        NxShape *shape = intersectingShapes[i];
        ShapeData *sd = (ShapeData*)shape->userData;
        if(sd != NULL)
            sd->isVisible = true;
    } 
    delete[] frustumCullingPlanes;
    delete[] intersectingShapes;
	
	//Actors
	int nbActors = scene->getNbActors();
	NxActor** actors = scene->getActors();
	while(nbActors--) {
		NxActor* actor = *actors++;
		if(actor->userData) {
			((ActorEntity*)actor->userData)->Draw();
		} else {
			NxShape* const* shapes = actor->getShapes();
			NxU32 shapesCount = actor->getNbShapes();
			for(NxU32 i=0; i<shapesCount; ++i) {
				NxShape* shape = shapes[i];
				if(shape->userData) {
                    ShapeData *sd = (ShapeData*)shape->userData;
                    if(sd->isVisible) {

                        glPushMatrix();
                        float glMat[16];
                        shape->getGlobalPose().getColumnMajor44(glMat);
                        glMultMatrixf(glMat);
                        Renderer::SetMaterial(*sd->srd);
                        if(shape->isBox()) {
                            Renderer::DrawBox(shape->isBox()->getDimensions());
                        } else if(shape->isSphere()) {
                            Renderer::DrawSphere(shape->isSphere()->getRadius());
                        }
                        glPopMatrix();

                        sd->isVisible = false;
                    }
				}
			}
		}
	}
	//pt.Next("actors");

	//Shadows
	glDisable(GL_LIGHTING);
	//SurfaceRenderingData shadowMaterial;
	//shadowMaterial.SetColor(0.0f, 0.0f, 0.0f, 0.8f);
	Renderer::SetMaterial(Renderer::shadowMaterial);
	nbActors = scene->getNbActors();
	actors = scene->getActors();
	while(nbActors--) {
		NxActor* actor = *actors++;
		if(actor->userData) {
			//shadow of entities is already drawn together with the actor itself
		} else {
			NxShape* const* shapes = actor->getShapes();
			NxU32 shapesCount = actor->getNbShapes();
			for(NxU32 i=0; i<shapesCount; ++i) {
				NxShape* shape = shapes[i];
				if(shape->userData) {
                    ShapeData *sd = (ShapeData*)shape->userData;
                    if(sd->isVisible) {

                        const float shadowHeight = 0.001f;
                        const static float shadowMat[] = { 1,0,0,0, 0,shadowHeight,0,0, 0,0,1,0, 0,0,0,1 };

                        NxMat34 gp = shape->getGlobalPose();

                        glPushMatrix();

                        const bool rayShadows = false;
                        if(rayShadows) {
                            NxRay ray;
                            NxVec3 sunDir(0,-1,0);
                            ray.dir = sunDir;
                            ray.orig = shape->getGlobalPosition();
                            NxRaycastHit hit;
                            NxShape* closestShape = scene->raycastClosestShape(ray, NX_ALL_SHAPES, hit);

                            NxVec3 hitPos = hit.worldImpact;
                            glTranslatef(hitPos.x, hitPos.y+shadowHeight, hitPos.z);
                            glMultMatrixf(shadowMat);
                            float glRotMat[16];
                            gp.t = zeroVec3;
                            gp.getColumnMajor44(glRotMat);
                            glMultMatrixf(glRotMat);
                        } else {
                            glMultMatrixf(shadowMat);
                            float glMat[16];
                            gp.getColumnMajor44(glMat);
                            glMultMatrixf(glMat);
                        }

                        if(shape->isBox()) {
                            Renderer::DrawBox(shape->isBox()->getDimensions());
                        } else if(shape->isSphere()) {
                            Renderer::DrawSphere(shape->isSphere()->getRadius());
                        }
                        glPopMatrix();

                        sd->isVisible = false;
                    }					
				}
			}
		}
	}
	glEnable(GL_LIGHTING);
	//pt.Next("shadows");

	for(int i=0; i<Flag::all.Size(); ++i) {
		Flag::all[i]->Draw();
	}
	//pt.Next("cloth");

	glEnable(GL_ALPHA_TEST);
	glAlphaFunc(GL_GREATER, 0.5);
	Grass::DrawAll();
	Renderer::ResetMaterial();
	glDisable(GL_ALPHA_TEST);

	//Explosions
	glDepthMask(GL_FALSE);
	for(int i=0; i<Explosion::all.Size(); ++i) {
		Explosion::all[i]->Draw();
	}
	glDepthMask(GL_TRUE);

	//Debug Rendering
	if(debugRenderer.GetEnabled() == true) {
		debugRenderer.Draw();
	}
	//pt.Next("debug rendering");

	//HUD
	if(Game::IsHudEnabled()) {
		glEnable(GL_CULL_FACE);
		DrawHud(GL_RENDER);
	}
	//pt.Next("hud");

	//wind flag hud
	if(Game::IsHudEnabled() && !Flag::all.Empty() && !Game::IsHelpViewEnabled()) {
		Flag* flag = Flag::all[0];
		glViewport(Renderer::GetActiveScreenW()-flagViewW, 0, flagViewW, flagViewH);
		glClear(GL_DEPTH_BUFFER_BIT);
		NxVec3 cameraDir = camera.GetDirection();
		cameraDir.y = 0;
		cameraDir.normalize();
		const float dist = 9;
		NxVec3 pos = flag->GetPosition() - cameraDir * dist;
		pos.y += Flag::GetPoleHeight()*0.75f;
		hudCamera.SetDirection(cameraDir);
		hudCamera.SetPosition(pos);
		Renderer::Update(hudCamera);
		//glDisable(GL_LIGHTING);
		//skybox->Draw();
		//glEnable(GL_LIGHTING);
		//groundMaterial.SetTexture(Textures::TEXTURE_GROUND);
		//Renderer::SetMaterial(groundMaterial);
		//Renderer::DrawPlane(7.0f, 12, -0.01f);
		//glDisable(GL_CULL_FACE);
		flag->Draw();
		//glEnable(GL_CULL_FACE);
		//glPopMatrix();
		glViewport(0, 0, Renderer::GetActiveScreenW(), Renderer::GetActiveScreenH());
		Renderer::Update(camera);
	}
	//pt.Next("wind flag hud");

	glutSwapBuffers();
	//pt.Next("swap buffers");
}

void PhysicsLogic::DrawHud(GLenum mode) {
	if(mode == GL_RENDER) {
		Hud::Begin();
	}

	const int sw = Renderer::GetActiveScreenW();
	const int sh = Renderer::GetActiveScreenH();
	const float centerX = sw/2;
	const float centerY = sh/2;

	const float th = Hud::GetCharHeight();

	const float borderDist = 10;
	const bool textLinesFromTop = true;
	const float panelY = sh - flagViewH;

	const float textMargin = Hud::GetTextMargin();

	const float panelFakeDepth = 15.0f;

	const Player* currentPlayer = Game::GetCurrentPlayer();
	Cannon* selectedCannon = currentPlayer->GetSelectedCannon();

	//name panel:
	Renderer::SetMaterial(namePanelMaterial);
	Hud::Rect(0,0, 512, 52);

	const std::string& playerNameStr = currentPlayer->GetName();
	Hud::Text(playerNameStr.c_str(), 12, 26, false, textMaterial, textShadowMaterial);
	const std::string goldStr = StringBuilder() << "Gold: " << currentPlayer->GetGold();
	Hud::Text(goldStr.c_str(), 150, 26, false, textMaterial, textShadowMaterial);
	const std::string buildingsStr = StringBuilder() << "Häuser: " << currentPlayer->GetBuildingsCount();
	Hud::Text(buildingsStr.c_str(), 284, 26, false, textMaterial, textShadowMaterial);

	//message panel:
	if(hudMessageTimeToLive > 0.0f) {
		Hud::Text(hudMessage.c_str(), 12, 78, false, textMaterial, textShadowMaterial);
	}

	//bottom panel:
	Renderer::SetMaterial(bottomPanelMaterial);
	Hud::Rect(0, sh-200, 208, sh);

	//flag panel:
	Renderer::SetMaterial(flagPanelMaterial);
	Hud::Rect(sw-208, sh-189, sw, sh);

	Renderer::SetMaterial(flagPanelMaterial2);
	Hud::Rect(sw-flagViewW, panelY, sw, sh);

	//buttons:
	const float buttonW = 160;
	const float buttonH = 50;
	const float buttonMargin = 6;

	float buttonX = 0;
	float buttonY = 0;
	if(currentPlayer->IsInBuildMode()) {
		Player::PLACEABLE placable;
		std::string s;

		buttonX = buttonMargin;
		buttonY = sh-195-(buttonMargin+buttonH)*3-buttonMargin;

		Hud::SetHudElement(SHE_TOWER);
		Renderer::SetMaterial(buttonMaterial);
		Hud::Rect(buttonX, buttonY, buttonX+buttonW, buttonY+buttonH);
		placable = Player::PL_TOWER;
		s = Player::GetNameOfPlacable(placable);
		Hud::Text(s.c_str(), buttonX+buttonW*0.5f, buttonY+buttonH*0.5f-th*0.5f, true, textMaterial, textShadowMaterial);
		s = StringBuilder() << currentPlayer->GetCosts(placable) << " Gold";
		Hud::Text(s.c_str(), buttonX+buttonW*0.5f, buttonY+buttonH*0.5f+th*0.5f, true, textMaterial, textShadowMaterial);

		buttonY += buttonMargin+buttonH;

		Hud::SetHudElement(SHE_WALL_FRONT);
		Renderer::SetMaterial(buttonMaterial);
		Hud::Rect(buttonX, buttonY, buttonX+buttonW, buttonY+buttonH);
		placable = Player::PL_WALL_FRONT;
		s = Player::GetNameOfPlacable(placable);
		Hud::Text(s.c_str(), buttonX+buttonW*0.5f, buttonY+buttonH*0.5f-th*0.5f, true, textMaterial, textShadowMaterial);
		s = StringBuilder() << currentPlayer->GetCosts(placable) << " Gold";
		Hud::Text(s.c_str(), buttonX+buttonW*0.5f, buttonY+buttonH*0.5f+th*0.5f, true, textMaterial, textShadowMaterial);

		buttonY += buttonMargin+buttonH;

		Hud::SetHudElement(SHE_WALL_SIDE);
		Renderer::SetMaterial(buttonMaterial);
		Hud::Rect(buttonX, buttonY, buttonX+buttonW, buttonY+buttonH);
		placable = Player::PL_WALL_SIDE;
		s = Player::GetNameOfPlacable(placable);
		Hud::Text(s.c_str(), buttonX+buttonW*0.5f, buttonY+buttonH*0.5f-th*0.5f, true, textMaterial, textShadowMaterial);
		s = StringBuilder() << currentPlayer->GetCosts(placable) << " Gold";
		Hud::Text(s.c_str(), buttonX+buttonW*0.5f, buttonY+buttonH*0.5f+th*0.5f, true, textMaterial, textShadowMaterial);
	} else {
		if(selectedCannon) {
			std::string s;

			buttonX = buttonMargin;
			buttonY = sh-195-(buttonMargin+buttonH)*2-buttonMargin;

			Hud::SetHudElement(SHE_CANNON_BALL_NORMAL);
			Renderer::SetMaterial(buttonMaterial);
			Hud::Rect(buttonX, buttonY, buttonX+buttonW, buttonY+buttonH);
			Hud::Text("Normal", buttonX+buttonW*0.5f, buttonY+buttonH*0.5f, true, textMaterial, textShadowMaterial);

			buttonY += buttonMargin+buttonH;

			Hud::SetHudElement(SHE_CANNON_BALL_EXPLOSIVE);
			Renderer::SetMaterial(buttonMaterial);
			Hud::Rect(buttonX, buttonY, buttonX+buttonW, buttonY+buttonH);
			Hud::Text("Explosiv", buttonX+buttonW*0.5f, buttonY+buttonH*0.5f-th*0.5f, true, textMaterial, textShadowMaterial);
			s = StringBuilder() << CannonBall::GetExplosionCosts() << " Gold";
			Hud::Text(s.c_str(), buttonX+buttonW*0.5f, buttonY+buttonH*0.5f+th*0.5f, true, textMaterial, textShadowMaterial);
		}
	}

	if(true) {
		buttonX = sw-buttonMargin-buttonW;
		buttonY = sh-195-(buttonMargin+buttonH)*2-buttonMargin;

		Hud::SetHudElement(SHE_BUILD_OR_ATTACK);
		Renderer::SetMaterial(buttonMaterial);
		Hud::Rect(buttonX, buttonY, buttonX+buttonW, buttonY+buttonH);
		std::string s = currentPlayer->IsInBuildMode() ? "Angreifen" : "Bauen";
		Hud::Text(s.c_str(), buttonX+buttonW*0.5f, buttonY+buttonH*0.5f, true, textMaterial, textShadowMaterial);

		buttonY += buttonMargin+buttonH;

		Hud::SetHudElement(SHE_END_TURN);
		Renderer::SetMaterial(buttonMaterial);
		Hud::Rect(buttonX, buttonY, buttonX+buttonW, buttonY+buttonH);
		Hud::Text("Zug beenden", buttonX+buttonW*0.5f, buttonY+buttonH*0.5f, true, textMaterial, textShadowMaterial);

        Hud::SetHudElement(SHE_TOGGLE_CAM);
        if(cameraLocked) {
            Renderer::SetMaterial(buttonFixedCamMaterial);
        } else {
            Renderer::SetMaterial(buttonFreeCamMaterial);
        }
        buttonX = sw-flagViewW-buttonH-15;
        buttonY = sh-buttonMargin-buttonH;
        Hud::Rect(buttonX, buttonY, buttonX+buttonH, buttonY+buttonH);

	}

	if(!currentPlayer->IsInBuildMode()) {
		buttonX = sw*0.5f-buttonW*0.5f;
		buttonY = sh-buttonMargin-buttonH;

		if(!Game::waitingForNextTurn) {
			Hud::SetHudElement(SHE_SHOOT);
			Renderer::SetMaterial(buttonMaterial);
			Hud::Rect(buttonX, buttonY, buttonX+buttonW, buttonY+buttonH);
			Hud::Text("Schießen", buttonX+buttonW*0.5f, buttonY+buttonH*0.5f, true, textMaterial, textShadowMaterial);
		}
	} else {
		buttonX = sw*0.5f-buttonMargin*0.5f-buttonW;
		buttonY = sh-buttonMargin-buttonH;

		Hud::SetHudElement(SHE_PLACE);
		Renderer::SetMaterial(buttonMaterial);
		Hud::Rect(buttonX, buttonY, buttonX+buttonW, buttonY+buttonH);
		Hud::Text("Platzieren", buttonX+buttonW*0.5f, buttonY+buttonH*0.5f, true, textMaterial, textShadowMaterial);

		buttonX = sw*0.5f+buttonMargin*0.5f;

		Hud::SetHudElement(SHE_CLEAR);
		Renderer::SetMaterial(buttonMaterial);
		Hud::Rect(buttonX, buttonY, buttonX+buttonW, buttonY+buttonH);
		Hud::Text("Räumen", buttonX+buttonW*0.5f, buttonY+buttonH*0.5f, true, textMaterial, textShadowMaterial);
	}

	if(Game::WinnerEncountered()) {
		buttonX = sw*0.5f-buttonW*0.5f;
		buttonY = sh*0.5f-buttonH*0.5f+buttonH+buttonMargin;

		Hud::SetHudElement(SHE_RESET_GAME);
		Renderer::SetMaterial(buttonMaterial);
		Hud::Rect(buttonX, buttonY, buttonX+buttonW, buttonY+buttonH);
		Hud::Text("Neues Spiel", buttonX+buttonW*0.5f, buttonY+buttonH*0.5f, true, textMaterial, textShadowMaterial);
	}

	Hud::SetHudElement(SHE_NONE);

	int lineIndex = 1;

	if(currentPlayer->IsInBuildMode()) {
		//do nothing
	} else {
		if(selectedCannon) {
			const float deg = 180.0f/NxPiF32;
			const NxVec3 dir = selectedCannon->GetDirection();
			const float a = asin(dir.y);
			const float tx = cos(a);
			const float b = asin(dir.z/tx);
			const int n = 1;

			lineIndex++;

			const float tiltValue = round(a*deg, n);
			const std::string tilt = StringBuilder() << "Neigung: " << tiltValue << std::string(tiltValue == int(tiltValue) ? ".0" : "");
			const float rotationValue = round(b*deg, n);
			const std::string rotation = StringBuilder() << "Drehung: " << rotationValue << std::string(rotationValue == int(rotationValue) ? ".0" : "");
			const std::string firepower = StringBuilder() << "Kraft: " << round(selectedCannon->GetFirepower(), 0);
			Hud::Text(tilt.c_str(), borderDist, panelY + Hud::GetTextLineHeight(lineIndex++, textLinesFromTop), false, textMaterial, textShadowMaterial);
			Hud::Text(rotation.c_str(), borderDist, panelY + Hud::GetTextLineHeight(lineIndex++, textLinesFromTop), false, textMaterial, textShadowMaterial);
			Hud::Text(firepower.c_str(), borderDist, panelY + Hud::GetTextLineHeight(lineIndex++, textLinesFromTop), false, textMaterial, textShadowMaterial);

			lineIndex++;

			const std::string hp = StringBuilder() << "Haltbarkeit: " << round(selectedCannon->GetHitpoints(), 0);
			Hud::Text(hp.c_str(), borderDist, panelY + Hud::GetTextLineHeight(lineIndex++, textLinesFromTop), false, textMaterial, textShadowMaterial);
		}
	}

	if(Game::WinnerEncountered()) {
		Player* winner = Game::GetWinner();
		const std::string s = StringBuilder() << winner->GetName() << " hat gewonnen!";
		Hud::Text(s.c_str(), sw/2, sh*0.5f+12, true, textMaterial, textShadowMaterial);
	}

	if(Game::IsHelpViewEnabled()) {
		const float textIndent = 32;
		const float textTabSize = 150;

		int lineIndex = 1;

		Renderer::SetMaterial(PhysicsLogic::helpPanelMaterial);
		Hud::Rect(0, 0, sw, sh);

		Hud::Text("Tastenbelegung", centerX, Hud::GetTextLineHeight(lineIndex, true), true, textMaterial, textShadowMaterial);

		lineIndex += 2;

		Hud::Text("Allgemein:", textMargin, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 2;

		Hud::Text("F4:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("FPS Anzeige an/aus", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 1;

		Hud::Text("F5:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Gras umschalten", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 1;

		Hud::Text("F6:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Flagge umschalten", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 2;

		Hud::Text("E:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Freie Kamera an/aus", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 1;

		Hud::Text("W, A, S, D:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Steuerung der freien Kamera", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 2;

		Hud::Text("Ende, Eingabe:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Zug beenden", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 1;

		Hud::Text("B:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Umschalten zwischen Baumodus und Angriffsmodus", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 2;

		Hud::Text("Baumodus:", textMargin, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 2;

		Hud::Text("T, Z, U:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Platzierbare Objekte wählen", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 1;

		Hud::Text("Pfeiltasten:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Platzierungsbox bewegen", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 1;

		Hud::Text("Leertaste:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Objekt platzieren", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 2;

		Hud::Text("Angriffsmodus:", textMargin, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 2;

		Hud::Text("G:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Geschosse wählen", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 1;

		Hud::Text("Pfeiltasten:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Kanone ausrichten", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 1;

		Hud::Text("+, -:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Schusskraft der Kanone ändern", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 1;

		Hud::Text("Leertaste:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Mit Kanone schießen", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 1;

		Hud::Text("K:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Kanonenkugel Kamera an/aus", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);

		lineIndex += 1;

		Hud::Text("N:", textIndent, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);
		Hud::Text("Nächste Kanone wählen", textIndent+textTabSize, Hud::GetTextLineHeight(lineIndex, true), false, textMaterial, textShadowMaterial);


	}

    if(Game::IsFPSEnabled()) {
        const std::string fpsStr = StringBuilder() << "FPS: " << Renderer::fps;
        Hud::Text(fpsStr.c_str(), sw-85, 26, false, textMaterial, textShadowMaterial);
    }

	if(mode == GL_RENDER) {
		Hud::End();
	}
}

void PhysicsLogic::DrawPerfTestWindow() {
	glClear(GL_COLOR_BUFFER_BIT);
	if(pt.IsEnabled()) {
		pt.RenderLastValues();
	}
	glutSwapBuffers();
}

void PhysicsLogic::SimulateAndRender() {
	//pt.Start();
	PreTick();
	//pt.Next("PreTick");
	Draw();
	//pt.Next("Draw");
	PostTick();
	//pt.Next("PostTick");
}

NxVec3 PhysicsLogic::CameraToWorldGridVector(const NxVec3& v) {
	NxMat33 m;
	const float a = atan2(PhysicsLogic::GetCamera().GetDirection().z, PhysicsLogic::GetCamera().GetDirection().x);
	rotationVectorToMatrix(NxVec3(0,-a,0), &m);

	NxVec3 e = m * v;
	e.y = 0;

	NxVec3 r;
	float maxValue = 0.0f;
	if(e.x > maxValue) {
		maxValue = e.x;
		r.set(1,0,0);
	}
	if(e.x < -maxValue) {
		maxValue = -e.x;
		r.set(-1,0,0);
	}
	if(e.z > maxValue) {
		maxValue = e.z;
		r.set(0,0,1);
	}
	if(e.z < -maxValue) {
		maxValue = -e.z;
		r.set(0,0,-1);
	}

	return r;
}

void PhysicsLogic::SetWind(const NxVec3& force) {
	wind = force;
	for(int i=0; i<Flag::all.Size(); ++i) {
		Flag::all[i]->SetWind(wind);
	}
	Grass::SetWind(wind);
}

void PhysicsLogic::HudMessage(const std::string& message) {
	hudMessageTimeToLive = hudMessageMaxTimeToLive;
	hudMessage = message;
}

void PhysicsLogic::ToggleDebugRendering() {
	bool enabled = !debugRenderer.GetEnabled();
	debugRenderer.SetParameterEnabled(NX_VISUALIZE_COLLISION_SHAPES, enabled);
	debugRenderer.SetParameterEnabled(NX_VISUALIZE_COLLISION_STATIC, enabled);
	//debugRenderer.SetParameterEnabled(NX_VISUALIZE_COLLISION_SKELETONS, enabled);
	debugRenderer.SetParameterEnabled(NX_VISUALIZE_JOINT_WORLD_AXES, enabled);
	debugRenderer.SetParameterEnabled(NX_VISUALIZE_JOINT_LIMITS, enabled);
	debugRenderer.SetParameterEnabled(NX_VISUALIZE_WORLD_AXES, enabled);
	debugRenderer.SetParameterEnabled(NX_VISUALIZE_FORCE_FIELDS, enabled);
	debugRenderer.SetParameterEnabled(NX_VISUALIZE_CLOTH_MESH, enabled);
	debugRenderer.SetEnabled(enabled);

	HudMessage(StringBuilder() << "Debug Rendering " << std::string(enabled?"an":"aus"));
}

void PhysicsLogic::ToggleGrass(bool message) {
	const int i = Grass::GetCount();
	Grass::DeleteAll();
	switch(i) {
		case 1: Grass::Add(NxVec3(0, 0, 0), NxVec3(0, NxHalfPi, 0));
		case 0: Grass::Add(NxVec3(0, 0, 0), NxVec3(0, 0, 0));
	}

	if(message) {
		HudMessage(StringBuilder() << "Anzahl Gras: " << Grass::GetCount());
	}
}

void PhysicsLogic::ToggleFlag() {
	ArrayList<Flag*>& flags = Flag::all;
	if(!flags.Empty()) {
		for(int i=0; i<flags.Size(); ++i) {
			delete flags[i];
		}
		flags.Clear();
	} else {
		flags.Add(new Flag(NxVec3(0,0,50)));
	}

	HudMessage(StringBuilder() << "Flagge " << std::string(flags.Empty()?"aus":"an"));
}

void PhysicsLogic::ToggleCannonBallCamera() {
	cannonBallCameraEnabled = !cannonBallCameraEnabled;
	HudMessage(StringBuilder() << "Kanonenkugel Kamera " << std::string(cannonBallCameraEnabled?"an":"aus"));
}

void PhysicsLogic::ToggleCameraLock() {
	Player* currentPlayer = Game::GetCurrentPlayer();
	if(currentPlayer->IsInBuildMode()) {
		cameraLocked = !cameraLocked;
	} else {
		if(!cameraLocked) {
			if(currentPlayer->GetSelectedCannon()) {
				cameraLocked = true;
			}
		} else {
			cameraLocked = false;
		}
	}
	HudMessage(StringBuilder() << "Freie Kamera: " << std::string(cameraLocked?"aus":"an"));
}

void PhysicsLogic::ProcessKeyboardInput(unsigned char key, int x, int y) {
	if(key == 27) { //ESC key
		exit(0);
	} else if(key == 13) { //RETURN key
		Game::SelectNextPlayer();
	} else if(key == 'k') {
		ToggleCannonBallCamera();
	} else if(key == 'c') {
		if(Game::IsInDebugMode()) {
			const int n = 16;
			NxU32 numbers[n];

			numbers[0] = scene->getNbActorGroupPairs();
			numbers[1] = scene->getNbActors();
			numbers[2] = scene->getNbCloths();
			numbers[3] = scene->getNbCompartments();
			numbers[4] = scene->getNbDynamicShapes();
			numbers[5] = scene->getNbEffectors();
			numbers[6] = scene->getNbFluids();
			numbers[7] = scene->getNbForceFieldLinearKernels();
			numbers[8] = scene->getNbForceFields();
			numbers[9] = scene->getNbForceFieldShapeGroups();
			numbers[10] = scene->getNbJoints();
			numbers[11] = scene->getNbMaterials();
			numbers[12] = scene->getNbPairs();
			numbers[13] = scene->getNbSoftBodies();
			numbers[14] = scene->getNbStaticShapes();
			numbers[15] = scene->getTotalNbShapes();

			StringBuilder sb;
			sb << StringBuilder::newline;
			for(int i=0; i<n; ++i) {
				sb << numbers[i] << ", ";
			}
			Game::log.LogInfo(std::string("Numbers of physx objects: ").append(sb));
		}
	} else if(key == 'b') {
		Game::GetCurrentPlayer()->ToggleBuildMode();
	} else if(key == 'l') {
		Game::UserClear();
	} else if(key == 'o') {
		if(Game::IsInDebugMode()) {
			pt.PrintToFile();
			pt.Reset();
		}
	} else if(key == 'p') {
		if(Game::IsInDebugMode()) {
			if(pt.IsEnabled()) {
				pt.Reset();
			}
			pt.SetEnabled(!pt.IsEnabled());
		}
	} else if(key == 'z') {
		Player* currentPlayer = Game::GetCurrentPlayer();
		if(currentPlayer->IsInBuildMode()) {
			currentPlayer->SetPlaceable(Player::PL_WALL_FRONT);
		}
	} else if(key == 'u') {
		Player* currentPlayer = Game::GetCurrentPlayer();
		if(currentPlayer->IsInBuildMode()) {
			currentPlayer->SetPlaceable(Player::PL_WALL_SIDE);
		}
	} else if(key == 't') {
		Player* currentPlayer = Game::GetCurrentPlayer();
		if(currentPlayer->IsInBuildMode()) {
			currentPlayer->SetPlaceable(Player::PL_TOWER);
		}
	} else if(key == 'e') {
		ToggleCameraLock();
	} else if(key == ' ') {
		Player* currentPlayer = Game::GetCurrentPlayer();
		if(!currentPlayer->IsInBuildMode()) {
			Game::UserShoot();
		} else {
			Game::UserPlace();
		}
	} else if(key == 'x') {
		if(Game::IsInDebugMode()) {
			Cannon* selectedCannon = Game::GetCurrentPlayer()->GetSelectedCannon();
			float firepower = 50;
			if(selectedCannon) {
				firepower = selectedCannon->GetFirepower();
			}
			new CannonBall(NULL, camera.GetPosition(), camera.GetDirection()*firepower, wind, Game::GetCurrentPlayer()->GetExplosiveCannonBallsSelected());
		}
	} else if(key == 'v') {
		if(Game::IsInDebugMode()) {
			Game::SetRandomWind();
		}
	} else if(key == 'n') {
		Game::GetCurrentPlayer()->SelectNextCannon();
	} else if(key == '+') {
		Cannon* cannon = Game::GetCurrentPlayer()->GetSelectedCannon();
		if(cannon) {
			cannon->IncreaseFirepower();
		}
	} else if(key == '-') {
		Cannon* cannon = Game::GetCurrentPlayer()->GetSelectedCannon();
		if(cannon) {
			cannon->DecreaseFirepower();
		}
	} else if(key == 'd') {
		NxVec3 dist = camera.GetSideNormal();
		dist.normalize();
		const float currentVel = cameraVelocity.magnitude();
		cameraVelocity += dist*(0.3f+currentVel*0.25f);
	} else if(key == 'a') {
		NxVec3 dist = -camera.GetSideNormal();
		dist.normalize();
		const float currentVel = cameraVelocity.magnitude();
		cameraVelocity += dist*(0.3f+currentVel*0.25f);
	} else if(key == 'w') {
		NxVec3 dist = camera.GetDirection();
		dist.normalize();
		const float currentVel = cameraVelocity.magnitude();
		cameraVelocity += dist*(0.3f+currentVel*0.25f);
	} else if(key == 's') {
		NxVec3 dist = -camera.GetDirection();
		dist.normalize();
		const float currentVel = cameraVelocity.magnitude();
		cameraVelocity += dist*(0.3f+currentVel*0.25f);
	} else if(key == 'g') {
		Player* currentPlayer = Game::GetCurrentPlayer();
		currentPlayer->SetExplosiveCannonBallsSelected(!currentPlayer->GetExplosiveCannonBallsSelected());
	} else if(key == 'q') {
		if(Game::IsInDebugMode()) {
			Game::Reset(true);
		}
	}
}

void PhysicsLogic::ProcessKeyboardSpecialInput(int key, int x, int y) {
	if(key == GLUT_KEY_LEFT) {
		Player* currentPlayer = Game::GetCurrentPlayer();
		if(currentPlayer->IsInBuildMode()) {
			const NxVec3 r = CameraToWorldGridVector(NxVec3(0,0,-1));
			currentPlayer->MovePlacementSlot(r);
		} else {
			Cannon* cannon = currentPlayer->GetSelectedCannon();
			if(cannon) {
				cannon->Rotate(D2_LEFT);
			}
		}
	} else if(key == GLUT_KEY_RIGHT) {
		Player* currentPlayer = Game::GetCurrentPlayer();
		if(currentPlayer->IsInBuildMode()) {
			const NxVec3 r = CameraToWorldGridVector(NxVec3(0,0,1));
			currentPlayer->MovePlacementSlot(r);
		} else {
			Cannon* cannon = currentPlayer->GetSelectedCannon();
			if(cannon) {
				cannon->Rotate(D2_RIGHT);
			}
		}
	} else if(key == GLUT_KEY_UP) {
		Player* currentPlayer = Game::GetCurrentPlayer();
		if(currentPlayer->IsInBuildMode()) {
			const NxVec3 r = CameraToWorldGridVector(NxVec3(1,0,0));
			currentPlayer->MovePlacementSlot(r);
		} else {
			Cannon* cannon = currentPlayer->GetSelectedCannon();
			if(cannon) {
				cannon->Rotate(D2_UP);
			}
		}
	} else if(key == GLUT_KEY_DOWN) {
		Player* currentPlayer = Game::GetCurrentPlayer();
		if(currentPlayer->IsInBuildMode()) {
			const NxVec3 r = CameraToWorldGridVector(NxVec3(-1,0,0));
			currentPlayer->MovePlacementSlot(r);
		} else {
			Cannon* cannon = currentPlayer->GetSelectedCannon();
			if(cannon) {
				cannon->Rotate(D2_DOWN);
			}
		}
	} else if(key == GLUT_KEY_PAGE_UP) {
		const NxVec3& dist = camera.GetUpNormal();
		const float currentVel = cameraVelocity.magnitude();
		cameraVelocity += dist*(0.4f+currentVel*0.1f);
	} else if(key == GLUT_KEY_PAGE_DOWN) {
		const NxVec3& dist = -camera.GetUpNormal();
		const float currentVel = cameraVelocity.magnitude();
		cameraVelocity += dist*(0.4f+currentVel*0.1f);
	} else if(key == GLUT_KEY_END) {
		Game::SelectNextPlayer();
	} else if(key == GLUT_KEY_F1) {
		Game::ToggleHelpView();
	} else if(key == GLUT_KEY_F2) {
		if(Game::IsInDebugMode()) {
			ToggleDebugRendering();
		}
	} else if(key == GLUT_KEY_F3) {
		if(Game::IsInDebugMode()) {
			Game::ToggleHud();
		}
	} else if(key == GLUT_KEY_F4) {
		Game::ToggleFPS();
	} else if(key == GLUT_KEY_F5) {
		ToggleGrass(true);
	} else if(key == GLUT_KEY_F6) {
		ToggleFlag();
    }
}

void PhysicsLogic::ProcessMouseClick(int button, int state, int x, int y) {
	mouseButtonState[button] = state;

	if ((button == GLUT_LEFT_BUTTON) && (state == GLUT_DOWN)) {
		const int currentGlTime = glutGet(GLUT_ELAPSED_TIME);
		static const int range = 5;
		bool doubleClick = false;
		if(lastClickGlTime >= 0 && currentGlTime - lastClickGlTime <= 500 && abs(x-lastClickX) <= range && abs(y-lastClickY) <= range) {
			//double click encountered
			doubleClick = true;
			lastClickGlTime = -1;
		} else {
			lastClickGlTime = currentGlTime;
		}

		Player* currentPlayer = Game::GetCurrentPlayer();

		SELECTABLE_HUD_ELEMENT selectedElement = (SELECTABLE_HUD_ELEMENT)Hud::CheckHit(x, y);
		if(selectedElement != SHE_NONE) {
			switch(selectedElement) {
				case SHE_TOWER:					currentPlayer->SetPlaceable(Player::PL_TOWER); break;
				case SHE_WALL_FRONT:			currentPlayer->SetPlaceable(Player::PL_WALL_FRONT); break;
				case SHE_WALL_SIDE:				currentPlayer->SetPlaceable(Player::PL_WALL_SIDE); break;
				case SHE_CANNON_BALL_NORMAL:	Game::GetCurrentPlayer()->SetExplosiveCannonBallsSelected(false); break;
				case SHE_CANNON_BALL_EXPLOSIVE:	Game::GetCurrentPlayer()->SetExplosiveCannonBallsSelected(true); break;
				case SHE_END_TURN:				Game::SelectNextPlayer(); break;
				case SHE_BUILD_OR_ATTACK:		currentPlayer->ToggleBuildMode(); break;
				case SHE_SHOOT:					Game::UserShoot(); break;
				case SHE_PLACE:					Game::UserPlace(); break;
				case SHE_CLEAR:					Game::UserClear(); break;
				case SHE_RESET_GAME:			Game::Reset(true); break;
                case SHE_TOGGLE_CAM:			ToggleCameraLock(); break;
			}
		}

		Renderer::Update(camera);

		const NxActor* selectedActor = PhysxPicking::SelectActor(x, y, scene);
		
		if(selectedActor && selectedActor->userData) {
			ActorEntity* entity = (ActorEntity*)selectedActor->userData;
			if(entity->GetType() == AET_CANNON) {
				Cannon* selectedCannon = (Cannon*)entity;
				int index = currentPlayer->cannons.IndexOf(selectedCannon);
				if(index != -1) {
					currentPlayer->SetSelectedCannonIndex(index);
					if(doubleClick) {
						if(currentPlayer->IsInBuildMode()) {
							currentPlayer->ToggleBuildMode();
							cameraLocked = true;
						} else {
							ToggleCameraLock();
						}
					}
				}
			}
		}

		lastClickX = x;
		lastClickY = y;

		lastMousePosX = x;
		lastMousePosY = y;
	}
}

void PhysicsLogic::ProcessMouseState(int x, int y) {

}

void PhysicsLogic::ProcessMouseMovement(int x, int y) {
	const float mouseSensivity = Game::GetMouseSensivity();
	Player* currentPlayer = Game::GetCurrentPlayer();

	if(cameraLocked) {
		if(currentPlayer->IsInBuildMode()) {
			if(mouseButtonState[GLUT_RIGHT_BUTTON] == GLUT_DOWN) {
				camera.Rotate(mouseSensivity * x, 0.75f * mouseSensivity * y);
			} else if(mouseButtonState[GLUT_LEFT_BUTTON] == GLUT_DOWN) {
				const NxVec3 posLast = PhysxPicking::GroundIntersection(lastMousePosX, lastMousePosY);
				if( posLast.x > cameraBounds.x || posLast.x < -cameraBounds.x || posLast.z > cameraBounds.z || posLast.z < -cameraBounds.z) {
					//Grabbed ground outside allowed camera position. Do nothing.
				} else {
					const NxVec3 posCurrent = PhysxPicking::GroundIntersection(lastMousePosX+x, lastMousePosY+y);
					const NxVec3 diff = posLast-posCurrent;
					currentPlayer->MovePlacementSlot(diff/Game::GetPlacementSnapDistance());
				}
			}
		} else {
			Cannon* cannon = Game::GetCurrentPlayer()->GetSelectedCannon();
			if(cannon) {
				if(mouseButtonState[GLUT_RIGHT_BUTTON] == GLUT_DOWN) {
					cannon->Rotate(D2_RIGHT, x);
					cannon->Rotate(D2_DOWN, y);
				} else if(mouseButtonState[GLUT_LEFT_BUTTON] == GLUT_DOWN) {
					cannon->ChangeFirepowerBy(-y*mouseSensivity*0.005f);
				}
			}
		}
	} else {
		if(mouseButtonState[GLUT_RIGHT_BUTTON] == GLUT_DOWN) {
			camera.Rotate(mouseSensivity * x, 0.75f * mouseSensivity * y);
		} else if(mouseButtonState[GLUT_LEFT_BUTTON] == GLUT_DOWN) {
			const NxVec3 posLast = PhysxPicking::GroundIntersection(lastMousePosX, lastMousePosY);
			if( posLast.x > cameraBounds.x || posLast.x < -cameraBounds.x || posLast.z > cameraBounds.z || posLast.z < -cameraBounds.z) {
				//Grabbed ground outside allowed camera position. Do nothing.
			} else {
				const NxVec3 posCurrent = PhysxPicking::GroundIntersection(lastMousePosX+x, lastMousePosY+y);
				camera.MoveBy(posLast-posCurrent);
			}
		}
	}

	lastMousePosX += x;
	lastMousePosY += y;
}

void PhysicsLogic::ProcessMenuInput(int option) {
	switch(option) {
		case Renderer::MENU_DEBUG_RENDERING:
			if(Game::IsInDebugMode()) {
				ToggleDebugRendering();
			}
			break;
		case Renderer::MENU_HUD:
			Game::ToggleHud();
			break;
		case Renderer::MENU_CANNON_BALL_CAMERA:
			ToggleCannonBallCamera();
			break;
		case Renderer::MENU_HELP:
			Game::ToggleHelpView();
			break;
	}
}














